#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "astr.h"
#include "base.h"
#include "ini.h"

static inline ini_parameter_t *__ini_parameter_init(const char *kv)
{
    if (kv == NULL)
        return NULL;
    char c = '\0';
    uint64_t k_len = 0;
    uint64_t v_len = 0;
    uint64_t *len = &k_len;
    const char *kv_tmp = kv;
    while ((c = *kv_tmp) != '\0')
    {
        if (c == '=')
            len = &v_len;
        (*len)++;
        kv_tmp++;
    }
    // 未成功切换到val
    if (len == &k_len)
        return NULL;

    ini_parameter_t *ret = INI_MALLOC(sizeof(ini_parameter_t));
    if (ret == NULL)
        goto fail0;
    ret->key = INI_MALLOC(k_len + 1);
    if (ret->key == NULL)
        goto fail1;
    strncpy(ret->key, kv, k_len);

    ret->val = INI_MALLOC(v_len + 1);
    if (ret->val == NULL)
        goto fail1;
    strncpy(ret->val, kv + k_len + 1, v_len); // 跳过 '='
    goto fail0;
fail1:
    if (ret->key != NULL)
        INI_FREE(ret->key);
    if (ret != NULL)
        INI_FREE(ret);
fail0:
    return ret;
}

static inline void __ini_parameter_deinit(void *ptr)
{
    if (ptr == NULL)
        return;
    ini_parameter_t *p = (ini_parameter_t *)ptr;
    if (p->key != NULL)
        INI_FREE(p->key);
    if (p->val != NULL)
        INI_FREE(p->val);
    if (p != NULL)
        INI_FREE(p);
}

static inline ini_section_t *__ini_section_init(const char *section)
{
    ini_section_t *ret = INI_MALLOC(sizeof(ini_section_t));
    if (ret == NULL)
        goto fail0;
    size_t len = strlen(section);
    ret->key = INI_MALLOC(len + 1);
    if (ret->key == NULL)
        goto fail1;
    strcpy(ret->key, section);

    ret->parameter_arr = array_init(__ini_parameter_deinit);
    if (ret->parameter_arr == NULL)
        goto fail1;
    goto fail0;
fail1:
    if (ret->key != NULL)
    {
        INI_FREE(ret->key);
    }
    if (ret != NULL)
    {
        INI_FREE(ret);
        ret = NULL;
    }
fail0:
    return ret;
}

static inline void __ini_section_deinit(void *ptr)
{
    if (ptr == NULL)
        return;
    ini_section_t *p = (ini_section_t *)ptr;
    if (p->parameter_arr != NULL)
        array_deinit(&(p->parameter_arr));
    if (p->key != NULL)
        INI_FREE(p->key);
    if (p)
        INI_FREE(p);
}

static inline ini_t *__ini_init()
{
    ini_t *ret = INI_MALLOC(sizeof(ini_t));
    if (ret == NULL)
        goto fail0;
    ret->no_section_parameter_arr = array_init(__ini_parameter_deinit);
    ret->section_arr = array_init(__ini_section_deinit);
fail0:
    return ret;
}

enum
{
    INI_READING_SECTION = 0,
    INI_READING_PARAMETERS,
    INI_READING_NOTES,
};

ini_t *ini_parse_str(const char *data)
{
    ini_t *ret = __ini_init();
    if (ret == NULL)
        goto fail0;
    astr_t *buf = astr_init(NULL);
    if (buf == NULL)
        goto fail1;

    char state = INI_READING_PARAMETERS; // 状态机
    char c = 0;
    while ((c = *data) != '\0')
    {
        if (c == ';')
        {
            state = INI_READING_NOTES;
        }
        else if (c == '[')
        {
            state = INI_READING_SECTION;
        }
        else if (((c == '\r' && *(data + 1) == '\n') || c == '\n'))
        {
            if (state == INI_READING_SECTION) // section数据
            {
                ini_section_t *section = __ini_section_init(buf->data);
                if (section != NULL)
                    array_add(ret->section_arr, section);
            }
            else if (state == INI_READING_PARAMETERS) // 键值对
            {
                ini_parameter_t *parameter = __ini_parameter_init(buf->data);
                if (parameter != NULL)
                {
                    if (ret->section_arr->len > 0)
                    {
                        ini_section_t *last_section = (ini_section_t *)array_get(ret->section_arr, ret->section_arr->len - 1);
                        array_add(last_section->parameter_arr, parameter);
                    }
                    else
                    {
                        array_add(ret->no_section_parameter_arr, parameter);
                    }
                }
            }
            // 切换 读取键值对
            state = INI_READING_PARAMETERS;
            if (buf->len != 0)
                astr_reset(buf);
        }

        if (c != '[' && c != ']' && c != '\r' && c != '\n' && state != INI_READING_NOTES)
        {
            astr_add(buf, c);
        }

        data++;
    }
    goto fail0;
fail1:
    if (ret != NULL)
        ini_free(&ret);
fail0:
    if (buf != NULL)
        astr_deinit(&buf);
    return ret;
}

ini_t *ini_parse_file(const char *file_path)
{
    ini_t *ini = NULL;
    if (file_path == NULL)
        goto fail0;
    size_t file_size = fsize(file_path);
    if (file_size == -1)
        goto fail0;
    FILE *txt = fopen(file_path, "rb");
    if (txt == NULL)
        goto fail0;

    char *data = INI_MALLOC(file_size + 1);
    if (data == NULL)
        goto fail1;

    size_t n = fread((void *)data, sizeof(char), file_size, txt);
    if (n != file_size)
        goto fail1;
    if (ferror(txt))
        goto fail1;
    ini = ini_parse_str(data);
fail1:
    if (data == NULL)
        INI_FREE(data);
    fclose(txt);
fail0:
    return ini;
}

char *ini_find_parameter_val(ini_t *ini, const char *section, const char *parameter_key)
{
    if (ini == NULL)
        return NULL;
    char *ret = NULL;
    ini_parameter_t *parameter_head = NULL;
    if (section == NULL)
    {
        for (size_t i = 0; i < ini->no_section_parameter_arr->len; i++)
        {
            ini_parameter_t *parameter = (ini_parameter_t *)array_get(ini->no_section_parameter_arr, i);
            if (strcmp(parameter_key, parameter->key) == 0)
            {
                ret = parameter->val;
                break;
            }
        }
    }
    else
    {
        for (size_t i = 0; i < ini->section_arr->len; i++)
        {
            ini_section_t *s = (ini_section_t *)array_get(ini->section_arr, i);
            if (strcmp(section, s->key) == 0)
            {
                for (size_t i = 0; i < s->parameter_arr->len; i++)
                {
                    ini_parameter_t *parameter = (ini_parameter_t *)array_get(s->parameter_arr, i);
                    if (strcmp(parameter_key, parameter->key) == 0)
                    {
                        ret = parameter->val;
                        break;
                    }
                }
            }
        }
    }
    return ret;
}

array_t *ini_find_parameters(ini_t *ini, const char *section)
{
    if (ini == NULL)
        return NULL;
    char *ret = NULL;
    array_t *parameters = NULL;
    if (section == NULL)
    {
        parameters = ini->no_section_parameter_arr;
    }
    else
    {
        for (size_t i = 0; i < ini->section_arr->len; i++)
        {
            ini_section_t *s = (ini_section_t *)array_get(ini->section_arr, i);
            if (strcmp(section, s->key) == 0)
            {
                parameters = s->parameter_arr;
                break;
            }
        }
    }
    return parameters;
}

void ini_free(ini_t **pp)
{
    if (*pp == NULL)
        return;
    ini_t *ptr = *pp;
    if (ptr->no_section_parameter_arr != NULL)
        array_deinit(&(ptr->no_section_parameter_arr));
    if (ptr->section_arr)
        array_deinit(&(ptr->section_arr));
    INI_FREE(ptr);
    *pp = NULL;
}

void ini_print(ini_t *ini)
{
    if (ini == NULL)
        return;

    if (ini->no_section_parameter_arr != NULL)
    {
        printf("==========no_section_parameters(%ld)==========\n", ini->no_section_parameter_arr->len);
        for (size_t i = 0; i < ini->no_section_parameter_arr->len; i++)
        {
            ini_parameter_t *parameter = (ini_parameter_t *)array_get(ini->no_section_parameter_arr, i);
            printf("\tkey:%s val:%s\n", parameter->key, parameter->val);
        }
        printf("==========no_section_parameters==========\n");
    }

    if (ini->section_arr != NULL)
    {
        printf("==========sections(%ld)==========\n", ini->section_arr->len);
        for (size_t i = 0; i < ini->section_arr->len; i++)
        {
            ini_section_t *section = (ini_section_t *)array_get(ini->section_arr, i);
            if (section != NULL)
            {
                if (section->parameter_arr != NULL)
                {
                    for (size_t j = 0; j < section->parameter_arr->len; j++)
                    {
                        ini_parameter_t *parameter = (ini_parameter_t *)array_get(section->parameter_arr, j);
                        printf("\tsection:%s key:%s val:%s\n", section->key, parameter->key, parameter->val);
                    }
                }
            }
        }
        printf("==========sections==========\n");
    }
}